/**
*	Header: world.h
*	
*	This header defines the most general world object holding the entire game world, so to
*	speak. Contains stuff like world.update() and world.draw().
*
*	
*/

#pragma once

#include <vector>
#include <list>
#include <libtcod.hpp>
#include <algorithm>
#include <time.h>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int_distribution.hpp>
#include <boost/lexical_cast.hpp>
#include "common.h"
#include "actor.h"
#include "fileio.h"
#include "camera.h"
#include "events.h"


class World {  // Don't derive
public:
	World();
	~World();
	
	void update(TCOD_key_t key, float elapsed);
	void updateViewport(Camera* camera);
	void draw (Camera* camera, TCODConsole* console = TCODConsole::root);
	
	void generateMap();

private:
	std::list <Actor*> actorsList;
	Player player;
	TileMap tileMap;
	EventHandler events;
};

World::World():tileMap(256,256) {
	boost::random::uniform_int_distribution<> distX(1, tileMap.getWidth()-2);
	boost::random::uniform_int_distribution<> distY(1, tileMap.getHeight()-2);
	boost::random::mt19937 gen;
	generateMap();
	actorsList.push_back( new Actor( distX(gen), distY(gen), &tileMap ) );
	actorsList.push_back( new Actor( distX(gen), distY(gen), &tileMap ) );
	actorsList.push_back( new Actor( distX(gen), distY(gen), &tileMap ) );
	actorsList.push_back( new Actor( distX(gen), distY(gen), &tileMap ) );
	actorsList.push_back( new Actor( distX(gen), distY(gen), &tileMap ) );
	actorsList.push_back(&player);
	FileIO fileio;
	//fileio.saveMap(&tileMap); // Fix save files
}

World::~World() {
	actorsList.remove(&player);
	std::for_each(actorsList.begin(), actorsList.end(), [=] (Actor* actor) {delete actor;});

}

void World::update(TCOD_key_t key, float elapsed) {
	player.handleInput(key, &tileMap);
	if (actorsList.empty() == true) std::cerr << "Actors List empty.\n";
	std::for_each(actorsList.begin(), actorsList.end(),
		[&] (Actor* actor) {
				actor->update(&tileMap, &actorsList);
		}
	);
	actorsList.remove_if(  // Clean up actor "corpses". Streamline into destructor?
		[&] (Actor* actor)->bool {
			if(actor->isDead()) {
				tileMap.getTile(actor->getX(),actor->getY())->actorList.remove(actor);
				return true;
			}
			else return false;
		}
	);
}

void World::updateViewport (Camera* camera) {
	if (player.getX()+camera->getSize()->first/2 > tileMap.getWidth()) {
		if (player.getY()+camera->getSize()->second/2 > tileMap.getHeight()-1)
			camera->centerOn(
				(tileMap.getWidth())-(camera->getSize()->first/2),
				(tileMap.getHeight())-(camera->getSize()->second/2)
			);
		else camera->centerOn_s(
				(tileMap.getWidth())-(camera->getSize()->first/2),
				player.getY()
			);
	}
	else if (player.getY()+camera->getSize()->second/2 > tileMap.getHeight())
		camera->centerOn_s(
			player.getX(),
			(tileMap.getHeight())-(camera->getSize()->second/2)
		);
	else camera->centerOn_s(player.getX(),player.getY());
}

unsigned int intensityShift() {
	/*boost::random::uniform_int_distribution<> dist(1, 100);
	static boost::random::mt19937 gen;
	return dist(gen);*/
	return std::clock()%50;
}

const float findIntensity (
	const int lightX, const int lightY,
	const int x, const int y, const float lightRadius = 25) {

	return (
		(lightRadius)
			/ ((lightRadius) + ((lightX-x)*(lightX-x)+(lightY-y)*(lightY-y)))
	);
}

void World::draw (Camera* camera, TCODConsole* console) {
	tileMap.computeFov(player.getX(),player.getY());
	for(unsigned int y = 0; y < camera->getHeight(); y++) {
		for(unsigned int x = 0; x < camera->getWidth(); x++) {
			if (tileMap.isInFov(x+camera->getPositionX(), y+camera->getPositionY()) == true) {
				if(tileMap.getTile(
					x+camera->getPositionX(),
					y+camera->getPositionY())->itemList.empty()) {
						TCODColor tempf = TCODColor::lerp(
								tileMap.getTile(
									x+camera->getPositionX(),
									y+camera->getPositionY()
								)->foreColour,
								TCODColor::white,
								findIntensity(
									player.getX(),
									player.getY(),
									x+camera->getPositionX(),
									y+camera->getPositionY()
								)
							);
						tempf.setValue(
							std::min<float>(
								tileMap.getTile(
									x+camera->getPositionX(),
									y+camera->getPositionY()
								)->foreColour.getValue(),
								TCODColor::lerp(
									TCODColor::black,
									TCODColor::yellow,
									findIntensity(
										player.getX(),
										player.getY(),
										x+camera->getPositionX(),
										y+camera->getPositionY()
									)
								).getValue()
							)
						);
						TCODColor tempb = TCODColor::lerp(
							tileMap.getTile(
								x+camera->getPositionX(),
								y+camera->getPositionY()
							)->backColour,
							TCODColor::white,
							findIntensity(
								player.getX(),
								player.getY(),
								x+camera->getPositionX(),
								y+camera->getPositionY()
							)
						);
						tempb.setValue(
							std::min<float>(
								tileMap.getTile(
									x+camera->getPositionX(),
									y+camera->getPositionY()
								)->backColour.getValue(),
								TCODColor::lerp(
									TCODColor::black,
									TCODColor::yellow,
									findIntensity(
										player.getX(),
										player.getY(),
										x+camera->getPositionX(),
										y+camera->getPositionY()
									)
								).getValue()
							)
						);
						console->putCharEx(
							x,
							y,
							tileMap.getTile(
								x+camera->getPositionX(),
								y+camera->getPositionY()
							)->c,
							tempf,
							tempb
						);
				}
				else {
					TCODColor tempf = TCODColor::lerp(
						tileMap.getTile(
							x+camera->getPositionX(),
							y+camera->getPositionY()
						)->itemList.back()->getColour(),
						TCODColor::white,
						findIntensity(
							player.getX(),
							player.getY(),
							x+camera->getPositionX(),
							y+camera->getPositionY()
						)
					);
					tempf.setValue(
						std::min<float>(
							tileMap.getTile(
								x+camera->getPositionX(),
								y+camera->getPositionY()
							)->itemList.back()->getColour().getValue(),
							TCODColor::lerp(
								TCODColor::black,
								TCODColor::yellow,
								findIntensity(
									player.getX(),
									player.getY(),
									x+camera->getPositionX(),
									y+camera->getPositionY()
								)
							).getValue()
						)
					);
					TCODColor tempb = TCODColor::lerp(
						tileMap.getTile(
							x+camera->getPositionX(),
							y+camera->getPositionY()
						)->backColour,
						TCODColor::white,
						findIntensity(
							player.getX(),
							player.getY(),
							x+camera->getPositionX(),
							y+camera->getPositionY()
						)
					);
					tempb.setValue(
						std::min<float>(
							tileMap.getTile(
								x+camera->getPositionX(),
								y+camera->getPositionY()
							)->backColour.getValue(),
							TCODColor::lerp(
								TCODColor::black,
								TCODColor::yellow,
								findIntensity(
									player.getX(),
									player.getY(),
									x+camera->getPositionX(),
									y+camera->getPositionY()
								)
							).getValue()
						)
					);
					console->putCharEx(
						x,
						y,
						tileMap.getTile(
							x+camera->getPositionX(),
							y+camera->getPositionY()
						)->itemList.back()->getSymbol(),
						tempf,
						tempb
					);
				}
			}
		}
	}
	std::for_each(actorsList.begin(), actorsList.end(),
		[=] (Actor* actor) {
			if (tileMap.isInFov(actor->getX(),actor->getY())
				&& camera->isVisible(actor->getX(),actor->getY())
				) {
				console->putChar(
					-camera->getOffsetX(actor->getX()),
					-camera->getOffsetY(actor->getY()),
					actor->getSym()
				);
			}
		}
	);
	console->putChar(
		-camera->getOffsetX(player.getX()),
		-camera->getOffsetY(player.getY()),
		player.getSym()
	);
	TCODSystem* sys;
	console->print(0,0,boost::lexical_cast<std::string>(sys->getFps()).c_str());
}

void World::generateMap() {
	boost::random::uniform_int_distribution<> dist(0,1);
	boost::random::mt19937 gen2;
	for(unsigned int y = 1; y < tileMap.getHeight()-1; y++) {
		for(unsigned int x = 1; x < tileMap.getWidth()-1; x++) {
			tileMap.getTile(x, y)->c = 'A';
			tileMap.getTile(x, y)->backColour = TCODColor::darkestCrimson;
			tileMap.getTile(x, y)->backColour.setHue((float(x)/tileMap.getWidth())*360);
			tileMap.getTile(x, y)->backColour.setSaturation(float(y)/tileMap.getHeight());
			//if (dist(gen2)) tileMap.getTile(x, y)->itemList.push_back(new Item);
		}
	}
	for(unsigned int x = 0; x < tileMap.getWidth(); x++) {
		tileMap.getTile(x, 0)->c = 'X';
		tileMap.getTile(x, 0)->backColour = TCODColor::lightGrey;
		tileMap.getTile(x, 0)->passable = false;
		tileMap.getTile(x, 0)->transparent = false;
		tileMap.getTile(x, tileMap.getHeight()-1)->c = 'X';
		tileMap.getTile(x, tileMap.getHeight()-1)->backColour = TCODColor::lightGrey;
		tileMap.getTile(x, tileMap.getHeight()-1)->passable = false;
		tileMap.getTile(x, tileMap.getHeight()-1)->transparent = false;
		if ((x % 5 == 0)&&(dist(gen2))) {
			for(unsigned int y = 0; y < tileMap.getHeight()-1; y++) {
				if ((dist(gen2))) {
					tileMap.getTile(x, y)->c = 'X';
					tileMap.getTile(x, y)->backColour = TCODColor::lightGrey;
					tileMap.getTile(x, y)->passable = false;
					tileMap.getTile(x, y)->transparent = false;
				}
			}
		}
	}
	for(unsigned int y = 0; y < tileMap.getHeight()-1; y++) {
		tileMap.getTile(0, y)->c = 'X';
		tileMap.getTile(0, y)->backColour = TCODColor::lightGrey;
		tileMap.getTile(0, y)->passable = false;
		tileMap.getTile(0, y)->transparent = false;
		tileMap.getTile(tileMap.getWidth()-1, y)->c = 'X';
		tileMap.getTile(tileMap.getWidth()-1, y)->backColour = TCODColor::lightGrey;
		tileMap.getTile(tileMap.getWidth()-1, y)->passable = false;
		tileMap.getTile(tileMap.getWidth()-1, y)->transparent = false;
		if ((y % 5 == 0)&&(dist(gen2))) {
			for(unsigned int x = 0; x < tileMap.getHeight()-1; x++) {
				if ((dist(gen2))) {
					tileMap.getTile(x, y)->c = 'X';
					tileMap.getTile(x, y)->backColour = TCODColor::lightGrey;
					tileMap.getTile(x, y)->passable = false;
					tileMap.getTile(x, y)->transparent = false;
				}
			}
		}
	}
	/*tileMap.getTile(40, 25)->c = 'A';
	tileMap.getTile(40, 25)->backColour = TCODColor::darkestCrimson;
	tileMap.getTile(40, 25)->backColour.setHue((float(60)/80)*360);
	tileMap.getTile(40, 25)->backColour.setSaturation(float(10)/50);
	tileMap.getTile(40, 25)->passable = true;
	tileMap.getTile(40, 25)->transparent = true;*/
	tileMap.update();
	/*for(unsigned int y = 0; y < tileMap.getHeight()-1; y++) {
		for(unsigned int x = 0; x < tileMap.getWidth(); x++) {
			std::cout << sizeof(*tileMap.getTile(x,y)) << "\n";
		}
	}*/
}
